﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace LujHosting.Internal;

public class RootDependencyCollector {

  public static RootDependencyCollector New(Type rootType, IList<HostConfigureExt.ServiceItem> initList) {
    return new(rootType, rootType.Assembly.GetTypes(), initList);
  }

  public record Result(
    IList<Type> Concrete,
    IList<Item> Multi
  );

  public record Item(Type Service, Type Impl);

  public RootDependencyCollector(Type rootType, IList<Type> allTypes, IList<HostConfigureExt.ServiceItem> initList) {
    _rootType = rootType;
    _allTypes = allTypes;
    _initList = initList;
  }

  public Result Collect(IList<Type> groupEx) {
    HashSet<Type> excludeSet = _initList
      .Select(i => i.Type)
      .ToHashSet();

    Type[] candidate = CollectCandidate(_rootType, excludeSet).ToArray();
    IList<Type> parentList = CollectMultiParent(candidate, groupEx);

    Type[] concreteList = candidate
      .Where(t => t.IsClass && !t.IsAbstract)
      .ToArray();

    return new(concreteList, CollectMultiItem(parentList, concreteList));
  }

  IEnumerable<Type> CollectCandidate(Type type, ISet<Type> excludeSet) {
    // 避免循环依赖
    HashSet<Type> newExclude = new(excludeSet) { type };

    IEnumerable<Type> allInField = type.GetRuntimeFields()
      .Where(f => !f.IsStatic && f.IsPrivate && f.IsInitOnly)// && !f.IsLiteral)
      .Select(f => f.FieldType)
      .Where(t => !t.IsValueType)
      .Where(t => !newExclude.Contains(t))
      .SelectMany(t => CollectCandidate(t, newExclude));

    IList<Type> parentList = CollectMultiParent(allInField, Array.Empty<Type>());
    IEnumerable<Type> candidateEx = _allTypes
      .Where(t => parentList.Any(pt => t.IsAssignableTo(pt)))
      .SelectMany(t => CollectCandidate(t, newExclude));

    return new[] { type }
      .Concat(allInField)
      .Concat(candidateEx)
      .Distinct();
  }

  Type[] CollectMultiParent(IEnumerable<Type> candidate, IList<Type> parentEx) {
    IEnumerable<Type> parentInField = candidate
      .Where(t => t.IsInterface)
      .Where(t => t.IsAssignableTo(typeof(IEnumerable)))
      .Where(t => t.GenericTypeArguments.Length == 1)
      .Select(t => t.GenericTypeArguments[0]);

    return parentInField.Concat(parentEx)
      .Distinct()
      .ToArray();
  }

  // 搜集字段里的接口集合注入
  Item[] CollectMultiItem(IList<Type> allParent, IList<Type> allImpl) {
    var collectImpl = (Type parent) => allImpl
      .Where(t => t.IsAssignableTo(parent))
      .Select(t => new Item(parent, t));

    return allParent
      .SelectMany(collectImpl)
      .ToArray();
  }

  readonly Type _rootType;
  readonly IList<Type> _allTypes;

  readonly IList<HostConfigureExt.ServiceItem> _initList;
}
